/**
 * 6种区间操作
 * 1. 区间增加
 * 2. 区间倍乘
 * 3. 区间设置
 * 4. 区间和
 * 5. 区间平方和
 * 6. 区间立方和
 */
#include <bits/stdc++.h>
using namespace std;

char *__abc147, *__xyz258, __ma369[1000000];
#define __hv007() ((__abc147==__xyz258) && (__xyz258=(__abc147=__ma369)+fread(__ma369,1,100000,stdin),__abc147==__xyz258) ? EOF : *__abc147++)

int getInt(){
	int sgn = 1;
	char ch = __hv007();
	while( ch != '-' && ( ch < '0' || ch > '9' ) ) ch = __hv007();
	if ( '-' == ch ) {sgn = 0;ch=__hv007();}
 
	int ret = (int)(ch-'0');
	while( '0' <= (ch=__hv007()) && ch <= '9' ) ret = ret * 10 + (int)(ch-'0');
	return sgn ? ret : -ret;
}

using llt = long long;
llt const MOD = 998244353LL;


struct SegTree{ // 线段树带延迟

using llt = long long;

using value_type = array<llt, 3>; // 和, 平方和, 立方和  
vector<value_type> data; // 线段树

using lazy_type = pair<bool, array<llt, 3>>;  // <设置标记, {k, b, 设置}>
vector<lazy_type> lazy; // 延迟标记

/// 从下往上计算信息，要变动
value_type _up_(const value_type & ls, const value_type & rs) {
    // assert(0);
    return {(ls[0] + rs[0]) % MOD, (ls[1] + rs[1]) % MOD, (ls[2] + rs[2]) % MOD};
}

/// 从上往下计算信息，要变动
void _dn_(int t, int s, int e, const lazy_type & delta) {
    // assert(0);
    auto & v = data[t];
    auto sz = e - s + 1;

    if(delta.first){
        auto t = delta.second[2];
        v = {sz *t % MOD, sz * t % MOD * t % MOD, sz * t % MOD * t % MOD * t % MOD}; 
    }

    
    auto k = delta.second[0];
    auto b = delta.second[1];

    auto he = v[0], fang = v[1], li = v[2];

    v = {
        (k * he % MOD + sz * b % MOD) % MOD,
        (k * k % MOD * fang % MOD + sz * b % MOD * b % MOD + 2 * k * b % MOD * he % MOD) % MOD,
        (k * k % MOD * k % MOD * li % MOD + sz * b % MOD * b % MOD * b % MOD + 3 * k * k % MOD * b % MOD * fang % MOD + 3 * k * b % MOD * b % MOD * he % MOD) % MOD
    };

    auto & lz = lazy[t];
    if(delta.first) {
        lz = delta;
    }else{
        (lz.second[0] *= k) %= MOD;
        (((lz.second[1] *= k) %= MOD) += b) %= MOD;
    }  
    return;
}

/// 初始化，清零，不用动
void init(int n) {
    data.assign(n + 1 << 2, value_zero());
    lazy.assign(n + 1 << 2, lazy_zero());
}

/// 这个函数不用动
void build(int n) {
    _build(1, 1, n);
}

/// 几乎不用动
value_type query(int t, int s, int e, int a, int b) {
    if(a <= s and e <= b) {
        return data[t];
    }
    _pushDown(t, s, e);
    int mid = (s + e) >> 1;
    value_type ans = value_zero();
    if(a <= mid) ans = _up_(ans, query(lson(t), s, mid, a, b));
    if(mid < b) ans = _up_(ans, query(rson(t), mid + 1, e, a, b));
    return ans;
}

/// 几乎不用动
void modify(int t, int s, int e, int a, int b, const lazy_type & delta) {
    if(a <= s and e <= b) {
        _dn_(t, s, e, delta);
        return;
    }
    _pushDown(t, s, e);
    int mid = (s + e) >> 1;
    if(a <= mid) modify(lson(t), s, mid, a, b, delta);
    if(mid < b) modify(rson(t), mid + 1, e, a, b, delta);
    _pushUp(t);
    return;
}

/// 这个函数不用动
void _pushUp(int t) {
    data[t] = _up_(data[lson(t)], data[rson(t)]);
}

/// 这个函数几乎不用动
void _pushDown(int t, int s, int e) {
    if(lazy_zero() == lazy[t]) return;
    auto & lz = lazy[t];
    auto ls = lson(t), rs = rson(t);
    int mid = (s + e) >> 1;

    _dn_(ls, s, mid, lz);
    _dn_(rs, mid + 1, e, lz);

    lz = lazy_zero();
}


/// 几乎不用动
void _build(int t, int s, int e) {
    if(s == e) {
        llt x = s;
        data[t] = {x, x * x % MOD, x * x % MOD * x % MOD}; // 注意提供value_type的初始化
        return; 
    }
    int mid = (s + e) >> 1;
    _build(lson(t), s, mid);
    _build(rson(t), mid + 1, e);
    _pushUp(t);
}

/// 辅助函数，视延迟的类型而变动
static const lazy_type & lazy_zero() {
    static const lazy_type LAZY0 = {false, {1LL, 0LL, 0LL}};
    return LAZY0; 
}

/// 辅助函数，视线段树信息类型而变动
static const value_type & value_zero() {
    static const value_type VALUE0 = {0LL, 0LL, 0LL};
    return VALUE0;
}

/// 这两个函数不用变动
static int lson(int x) {return x << 1;}
static int rson(int x) {return lson(x) | 1;}


};


int N, M;
SegTree St;

int main(){
    // freopen("z.txt", "r", stdin);
    N = getInt(); M = getInt();
    St.init(N); St.build(N);
    for(int c,a,b,q=1;q<=M;++q){
        c = getInt(); a = getInt(); b = getInt();
        switch(c){
            case 1:{ // add
                llt d = getInt();
                d %= MOD;
                if(d < 0) d += MOD;
                St.modify(1, 1, N, a, b, {false, {1LL, d, 0LL}});
                break;
            }
            case 2:{ // mul
                llt d = getInt();
                d %= MOD;
                if(d < 0) d += MOD;
                St.modify(1, 1, N, a, b, {false, {d, 0LL, 0LL}});
                break;      
            }
            case 3:{ // set
                llt d = getInt();
                d %= MOD;
                if(d < 0) d += MOD;
                St.modify(1, 1, N, a, b, {true, {1LL, 0LL, d}});
                break;  
            }
            case 4:
            case 5:
            case 6:{
                auto ans = St.query(1, 1, N, a, b); 
                printf("%lld\n", ans[c - 4]); 
                break;  
            }
            default: assert(0);
        }
    }
    return 0;
}